/**
 * Copyright (c) 2008, Andrew Carter
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are 
 * permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer. Redistributions in binary form must reproduce 
 * the above copyright notice, this list of conditions and the following disclaimer in 
 * the documentation and/or other materials provided with the distribution. Neither the 
 * name of Andrew Carter nor the names of contributors may be used to endorse or promote 
 * products derived from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.acarter.scenemonitor.dialog;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;

import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.net.URL;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;

import com.acarter.scenemonitor.util.Constants;
import com.acarter.scenemonitor.util.VerticalLabelUI;

/**
 * <p>
 * A_ToolDialog
 * </p>
 * <p>
 * Abstract implementation providing features such that the content pane of the
 * dialog may be hidden/shown.
 * </p>
 * <p>
 * Copyright (c) 2008
 * </p>
 * 
 * @author Andrew Carter
 */
public abstract class A_ToolDialog extends JDialog {

	/**	 */
	private static final long serialVersionUID = 1L;

	/** The bar of the dialog used for dragging, contains the button panel. */
	private JPanel basePanel;

	/** Contains the close and show/hide buttons */
	private JPanel buttonPanel;

	/** The pseudo content pane */
	private Container contentPane;

	/** The content pane frame */
	private JPanel contentFrame;

	/** Flag indicates if the content pane is hidden */
	private boolean hidden;

	/** Flag indicates which side of the base panel the content pane is on */
	private boolean leftSide;

	/** Icon for the hide button */
	private Icon hideIcon;

	/** Icon for the show button */
	private Icon showIcon;

	/** Icon for the close button */
	private Icon closeIcon;

	/** Icon for the help button */
	private Icon helpIcon;

	/** Icon for the resize button */
	private Icon resizeIcon;

	/** Icon for the hide roll over button */
	private Icon hideOverIcon;

	/** Icon for the show roll over button */
	private Icon showOverIcon;

	/** Icon for the close roll over button */
	private Icon closeOverIcon;

	/** Icon for the help roll over button */
	private Icon helpOverIcon;

	/** Icon for the resize roll over button */
	private Icon resizeOverIcon;

	/** The actual show/hide button */
	private JLabel stateButton;

	/**
	 * Tracks the dialog translation used during show/hide operations so the
	 * dialog doesn't jump around
	 */
	private int tempTranslation;

	/** Flag use to indicate the dialog is being dragged */
	private boolean moveStart = false;

	/** Flag used to indicate the dialog is being resizes */
	private boolean resizeStart = false;

	/** Reference point for dialog dragging */
	private Point mouseClickPoint = null;

	/** Dimensions of the display, used to determine when to switch sides */
	private Dimension screenSize;

	/**
	 * Constructor.
	 * 
	 * @param owner
	 *            Frame which is the parent of this dialog, or null
	 * @param title
	 *            Title of the dialog
	 * @param modal
	 *            True if model, false otherwise
	 */
	public A_ToolDialog(Frame owner, String title, boolean modal) {

		super(owner, title, modal);

		hidden = false;
		leftSide = true;
		screenSize = getToolkit().getScreenSize();

		setUndecorated(true);

		build();
	}

	/**
	 * Construct the dialog and components.
	 */
	private void build() {

		initIcons();

		final JLabel close = new JLabel(closeIcon);
		close.setToolTipText("Close");

		close.addMouseListener(new MouseListener() {

			public void mouseClicked(MouseEvent e) {

				close();
			}

			public void mouseEntered(MouseEvent e) {

				close.setIcon(closeOverIcon);
			}

			public void mouseExited(MouseEvent e) {

				close.setIcon(closeIcon);
			}

			public void mousePressed(MouseEvent e) {
			}

			public void mouseReleased(MouseEvent e) {
			}
		});

		if (hidden)
			stateButton = new JLabel(showIcon);
		else
			stateButton = new JLabel(hideIcon);
		
		stateButton.setToolTipText("Show/Hide");
		stateButton.addMouseListener(new MouseListener() {

			public void mouseClicked(MouseEvent e) {

				onShowHide();
			}

			public void mouseEntered(MouseEvent e) {

				if (hidden)
					stateButton.setIcon(showOverIcon);
				else
					stateButton.setIcon(hideOverIcon);
			}

			public void mouseExited(MouseEvent e) {

				if (hidden)
					stateButton.setIcon(showIcon);
				else
					stateButton.setIcon(hideIcon);
			}

			public void mousePressed(MouseEvent e) {
			}

			public void mouseReleased(MouseEvent e) {
			}
		});

		final JLabel help = new JLabel(helpIcon);
		help.setToolTipText("Help");

		help.addMouseListener(new MouseListener() {

			public void mouseClicked(MouseEvent e) {

				onHelp();
			}

			public void mouseEntered(MouseEvent e) {

				help.setIcon(helpOverIcon);
			}

			public void mouseExited(MouseEvent e) {

				help.setIcon(helpIcon);
			}

			public void mousePressed(MouseEvent e) {
			}

			public void mouseReleased(MouseEvent e) {
			}
		});

		JLabel labelTitle = new JLabel(getTitle());
		labelTitle.setUI(new VerticalLabelUI(false));
		labelTitle.setFont(new Font("Helvetica", Font.BOLD, 11));

		buttonPanel = new JPanel();
		buttonPanel.setBorder(BorderFactory
				.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), BorderFactory.createEmptyBorder(4, 4, 4, 4)));
		buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS));

		buttonPanel.add(Box.createRigidArea(new Dimension(0, 5)));
		buttonPanel.add(close);
		buttonPanel.add(Box.createRigidArea(new Dimension(0, 10)));
		buttonPanel.add(stateButton);
		buttonPanel.add(Box.createRigidArea(new Dimension(0, 10)));
		buttonPanel.add(labelTitle);
		buttonPanel.add(Box.createGlue());
		buttonPanel.add(Box.createRigidArea(new Dimension(0, 15)));
		buttonPanel.add(help);
		buttonPanel.add(Box.createRigidArea(new Dimension(0, 5)));
		buttonPanel.addMouseMotionListener(new MouseMotionListener() {

			public void mouseDragged(MouseEvent mouseEvent) {

				if (moveStart)
					moveWindowTo(mouseEvent.getPoint());
			}

			public void mouseMoved(MouseEvent mouseEvent) {
			}

		});

		buttonPanel.addMouseListener(new MouseAdapter() {

			@Override
			public void mousePressed(MouseEvent mouseEvent) {

				mouseClickPoint = mouseEvent.getPoint();
				moveStart = true;

				onBeginDragWindow();
			}

			@Override
			public void mouseReleased(MouseEvent mouseEvent) {

				moveStart = false;

				onEndDragWindow();
			}
		});

		basePanel = new JPanel();
		basePanel.setLayout(new BorderLayout());
		basePanel.add(buttonPanel, BorderLayout.CENTER);

		super.setContentPane(basePanel);

		contentFrame = new JPanel(new BorderLayout());
		contentFrame.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 1, Color.GRAY));

		JPanel resizePanel = new JPanel();
		resizePanel.setLayout(new BoxLayout(resizePanel, BoxLayout.X_AXIS));
		resizePanel.setPreferredSize(new Dimension(12, 12));
		resizePanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 12));

		final JLabel resize = new JLabel(resizeIcon);
		resize.setToolTipText("Resize");
		resize.addMouseMotionListener(new MouseMotionListener() {

			public void mouseDragged(MouseEvent mouseEvent) {

				if (resizeStart) {
					// noel lynch 21-08-08 : changed this for java 5 compatability
					resizeWindowTo(MouseInfo.getPointerInfo().getLocation());
					//resizeWindowTo(mouseEvent.getLocationOnScreen());
				}
					
			}

			public void mouseMoved(MouseEvent mouseEvent) {
			}

		});

		resize.addMouseListener(new MouseListener() {

			public void mousePressed(MouseEvent mouseEvent) {
				
				// noel lynch 21-08-08 : changed this for java 5 compatability
				mouseClickPoint = MouseInfo.getPointerInfo().getLocation();
				//mouseClickPoint = mouseEvent.getLocationOnScreen();
				resizeStart = true;

				onBeginResizeWindow();
			}

			public void mouseReleased(MouseEvent mouseEvent) {

				resizeStart = false;

				onEndResizeWindow();
			}

			public void mouseClicked(MouseEvent arg0) {
			}

			public void mouseEntered(MouseEvent arg0) {

				resize.setIcon(resizeOverIcon);
			}
			
			public void mouseExited(MouseEvent arg0) {

				resize.setIcon(resizeIcon);
			}
		});

		resizePanel.add(Box.createGlue());
		resizePanel.add(resize);

		contentFrame.add(resizePanel);
	}

	private void initIcons() {

		URL imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "hide.png");

		if (imageURL != null)
			hideIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "show.png");

		if (imageURL != null)
			showIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "close.png");

		if (imageURL != null)
			closeIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "help.png");

		if (imageURL != null)
			helpIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "resize.png");

		if (imageURL != null)
			resizeIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "hide_over.png");

		if (imageURL != null)
			hideOverIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "show_over.png");

		if (imageURL != null)
			showOverIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "close_over.png");

		if (imageURL != null)
			closeOverIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "help_over.png");

		if (imageURL != null)
			helpOverIcon = new ImageIcon(imageURL);

		imageURL = A_ToolDialog.class.getClassLoader().getResource(Constants.DIALOGDATAPATH + "resize_over.png");

		if (imageURL != null)
			resizeOverIcon = new ImageIcon(imageURL);
	}

	/**
	 * Handles movement of the dialog during dragging.
	 * 
	 * @param point
	 *            The absolute location
	 */
	private void moveWindowTo(Point point) {

		int musDiffX = point.x - mouseClickPoint.x;
		int musDiffY = point.y - mouseClickPoint.y;

		setLocation(getLocation().x + musDiffX, getLocation().y + musDiffY);

		checkSide();
	}

	/**
	 * For subclasses to override which custom behaviors.
	 */
	protected void onBeginDragWindow() {

	}

	/**
	 * For subclasses to override which custom behaviors.
	 */
	protected void onEndDragWindow() {

	}

	/**
	 * Handles resizing of the dialog during dragging.
	 * 
	 * @param point
	 *            The absolute location
	 */
	private void resizeWindowTo(Point point) {

		int musDiffX = point.x - mouseClickPoint.x;
		int musDiffY = point.y - mouseClickPoint.y;

		final Dimension size = getContentPane().getPreferredSize();
		size.setSize(size.width + musDiffX, size.height + musDiffY);

		getContentPane().setPreferredSize(size);
		pack();

		onHide();
		onShow();
		mouseClickPoint = point;
	}

	/**
	 * For subclasses to override which custom behaviors.
	 */
	protected void onBeginResizeWindow() {

	}

	/**
	 * For subclasses to override which custom behaviors.
	 */
	protected void onEndResizeWindow() {

	}

	/**
	 * Sets the pseudo content pane of this dialog.
	 * 
	 * @param contentPane
	 *            The content pane
	 */
	@Override
	public void setContentPane(Container contentPane) {

		this.contentPane = contentPane;

		contentFrame.add(contentPane, BorderLayout.NORTH);

		if (leftSide)
			basePanel.add(contentFrame, BorderLayout.LINE_END);
		else
			basePanel.add(contentFrame, BorderLayout.LINE_START);
	}

	/**
	 * Returns the pseudo content pane of this dialog.
	 * 
	 * @return The pseudo content pane
	 */
	@Override
	public Container getContentPane() {

		return this.contentPane;
	}

	/**
	 * Hides the dialog.
	 */
	private void close() {

		this.setVisible(false);
	}

	/**
	 * 
	 */
	public abstract void onHelp();

	/**
	 * @return True if the content pane is hidden, false otherwise
	 */
	public boolean isHidden() {

		return hidden;
	}

	/**
	 * Changes the state of the content pane from hidden to shown, or shown to
	 * hidden.
	 */
	public void onShowHide() {

		hidden = !hidden;

		if (hidden) {
			stateButton.setIcon(showOverIcon);
			onHide();
		}
		else {
			stateButton.setIcon(hideOverIcon);
			onShow();
		}
	}

	/**
	 * Hides the content pane.
	 */
	private void onHide() {

		if (!leftSide) {
			tempTranslation = contentFrame.getBounds().width;

			Point loc = getLocation();
			loc.translate(tempTranslation, 0);
			setLocation(loc);
		}
		else
			tempTranslation = 0;

		basePanel.remove(contentFrame);

		pack();
	}

	/**
	 * Shows the content pane.
	 */
	private void onShow() {

		if (!leftSide) {
			Point loc = getLocation();
			loc.translate(-tempTranslation, 0);
			setLocation(loc);
		}
		else {
			Point loc = getLocation();
			loc.translate(tempTranslation, 0);
			setLocation(loc);
		}

		if (leftSide)
			basePanel.add(contentFrame, BorderLayout.LINE_END);
		else
			basePanel.add(contentFrame, BorderLayout.LINE_START);

		pack();
	}

	/**
	 * Determines the side of the content pane.
	 */
	private void checkSide() {

		if (leftSide) {
			if (screenSize.width - getBounds().getMaxX() < 0) {
				leftSide = false;

				onShowHide();
				onShowHide();
			}
		}
		else {
			if (getLocation().x < 0) {
				leftSide = true;

				onShowHide();
				onShowHide();
			}
		}
	}
}
